package v2ray

import (
	"context"
	"gitee.com/yysf-xin/dproxy/pkg/db"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"gitee.com/yysf-xin/dproxy/pkg/v2ray/clash"
	core "github.com/v2fly/v2ray-core/v5"
	"github.com/v2fly/v2ray-core/v5/features/outbound"
	"gopkg.in/yaml.v3"
	"gorm.io/gorm"
	"io"
	"net/http"
	"sync"
	"sync/atomic"
	"time"
)

type CheckClashTask struct {
	Info
	Target        *clash.RootClash
	OnlyNew       bool
	FirstCanUseId uint
}

func (t *CheckClashTask) CheckClash(ins *core.Instance, client *db.Client) {
	var (
		h           interface{}
		c           clash.ConfigClash
		limit       = make(chan struct{}, 10)
		wg          sync.WaitGroup
		ctx, cancel = context.WithCancel(context.Background())
		err         error
	)
	defer func() {
		cancel()
		t.Stop = true
		tools.Log.Println("total", len(t.Target.Proxies), "current", t.Checking, "success", t.Success)
		t.Target.Proxies = make([]*clash.ConfigClash, 0)
	}()

	go func() {
		ch := time.NewTicker(1 * time.Second)
		defer ch.Stop()
		var tmp = t.Checking
	a:
		for {
			select {
			case <-ch.C:
				if tmp != t.Checking {
					tools.Log.Println("total", len(t.Target.Proxies), "current", t.Checking, "success", t.Success)
					tmp = t.Checking
				}
			case <-ctx.Done():
				break a
			}
		}
	}()
	t.Success = 0
	t.Stop = false
	t.Total = len(t.Target.Proxies)
	t.FirstCanUseId = 0
	t.Checking = 0
	for i := range t.Target.Proxies {
		if t.Stop {
			break
		}
		v := t.Target.Proxies[i]
		h = nil
		h, err = CreateHandler(ins, v)

		if v.Id == 26 {
			println("aa")
		}
		if v.Id == 0 {
			c.CanUse = 0
			res := client.Db.First(&c, "server=? and port=?", v.Server, v.Port)
			if res.Error == nil {
				v.Id = c.Id
				v.CanUse = c.CanUse
				if t.OnlyNew {
					atomic.AddInt32(&t.Checking, 1)
					continue
				}
			}
		}

		if err == nil && h != nil {
			limit <- struct{}{}
			d := &Dialer{Ins: ins, Handler: h.(outbound.Handler)}
			wg.Add(1)
			go func() {
				defer func() {
					<-limit
					atomic.AddInt32(&t.Checking, 1)
					wg.Done()
				}()
				//start := time.Now()
				CanUse, _ := DoTest(&http.Transport{
					DialContext:       d.DialContext,
					DisableKeepAlives: true,
				})
				//end := time.Since(start)
				t.onResult(CanUse, client, v)

				if CanUse && t.FirstCanUseId == 0 {
					t.FirstCanUseId = v.Id
				}

				//info := model.ProxyInfo{
				//	Type:   model.CLASH,
				//	Target: v.Id,
				//}

				//rs := client.Db.First(&info, "type = ? and target = ?", info.Type, info.Target)
				//if rs.Error != nil {
				//	if CanUse {
				//		//client.Mux.Lock()
				//		//rs = client.Db.Create(&info)
				//		//client.Mux.Unlock()
				//	}
				//} else if v.CanUse <= 0 {
				//	client.Mux.Lock()
				//	rs = client.Db.Delete(&info)
				//	client.Mux.Unlock()
				//}

				//if v.CanUse <= 0 || rs.Error != nil {
				//	return
				//}

				//his := model.ProxyHistory{
				//	ProxyInfo: info,
				//	Speed:     uint16(end.Milliseconds()),
				//	Success:   CanUse,
				//	Google:    CanUse,
				//}

				//if e != nil {
				//	if er, ok := e.(*url.Error); ok {
				//		str := er.Err.Error()
				//		if !strings.HasPrefix(str, "io") {
				//			his.Error = e.Error()
				//		}
				//	} else {
				//		his.Error = e.Error()
				//	}
				//
				//}
				//his.Id = 0
				//if his.Error != "" {
				//	client.Mux.Lock()
				//	rs = client.Db.Create(&his)
				//	client.Mux.Unlock()
				//	if rs.Error != nil {
				//		tools.Log.Println(rs.Error)
				//	}
				//}

			}()
		} else {
			atomic.AddInt32(&t.Checking, 1)
			t.onResult(false, client, v)
		}
	}
	wg.Wait()
	t.Stop = true
}

func (t *CheckClashTask) onResult(CanUse bool, client *db.Client, v *clash.ConfigClash) (rs *gorm.DB) {
	if CanUse {
		if v.CanUse < 0 {
			v.CanUse = 0
		}
		v.CanUse++
		tools.Log.Println("success", v.Id, v.Name, v.Type, v.Server)
		atomic.AddInt32(&t.Success, 1)
	} else {
		t.Error++
		if v.CanUse > 10 {
			v.CanUse = 10
		}
		v.CanUse--
		tools.Log.Println("failed", v.Id, v.Name, v.Type, v.Server)
	}
	client.Mux.Lock()

	if v.Id == 0 {
		var c clash.ConfigClash
		// 防止重复插入数据
		err := client.Db.First(&c, "server=? and port=?", v.Server, v.Port).Error
		if err == nil {
			v.Id = c.Id
			v.CanUse += c.CanUse
		}
	}

	if v.Id == 0 {
		rs = client.Db.Create(v)
	} else if v.CanUse == 0 {
		rs = client.Db.Model(v).Update("can_use", v.CanUse)
	} else {
		rs = client.Db.Save(v)
	}
	client.Mux.Unlock()
	if rs.Error != nil {
		tools.Log.Println(rs.Error)
	}
	return
}

func NewCheckClashTask(proxies ...*clash.ConfigClash) *CheckClashTask {
	return &CheckClashTask{
		Target: &clash.RootClash{
			Proxies: proxies,
		},
		OnlyNew: false,
	}
}

func LoadClashFromUrl(url string) (*CheckClashTask, error) {
	rs, err := http.Get(url)
	if rs != nil {
		defer rs.Body.Close()
	}
	if err != nil {
		return nil, err
	}
	return LoadClashFromYml(rs.Body)
}

func LoadClashFromYml(r io.Reader) (task *CheckClashTask, err error) {
	cfg := &clash.RootClash{}
	err = yaml.NewDecoder(r).Decode(cfg)
	if err != nil {
		return
	}
	task = &CheckClashTask{
		Target: cfg,
	}
	return
}
